#ifndef FHE_NODE_H
#define FHE_NODE_H

#include <fhe/core/Func.h>
#include <fhe/core/Var.h>
#include <boost/intrusive_ptr.hpp>
#include <map>
#include <set>

namespace fhe
{
  namespace core
  {
    class Node;
  }
}

namespace boost
{
  FHE_API void intrusive_ptr_add_ref( fhe::core::Node* node );
  FHE_API void intrusive_ptr_release( fhe::core::Node* node );
}

namespace fhe
{
  namespace core
  {
    class FHE_API Node
    {
      public:
        typedef boost::intrusive_ptr< Node > Ptr;
        
        Node();
        virtual ~Node();
        
        static Ptr load( const std::string& filename );
        static Ptr load( const Data::Ptr& data );
        void save( const std::string& filename ) const;
        Data::Ptr save() const;
        
        std::string module() const;
        void setModule( const std::string& module );
        std::string type() const;
        void setType( const std::string& type );
        bool is( const std::string& type ) const;
        void addSuper( const std::string& super );
        
        Ptr parent() const;
        Ptr root() const;
        
        void attachToParent( const Ptr& parent );
        void detachFromParent();
        
        typedef std::vector< Ptr >::const_iterator ChildIterator;
        ChildIterator childrenBegin() const;
        ChildIterator childrenEnd() const;
        void addChild( const Ptr& child );
        void removeChild( const Ptr& child );
        bool hasChild( const Ptr& child ) const;
        std::vector< Ptr > getChildren( const std::string& type ) const;
        
        bool hasFunc( const std::string& name ) const;
        IFunc::Ptr getFunc( const std::string& name ) const;
        void addFunc( const IFunc::Ptr& func );
        typedef std::map< std::string, IFunc::Ptr >::const_iterator FuncIterator;
        FuncIterator funcsBegin() const;
        FuncIterator funcsEnd() const;
        
        bool hasVar( const std::string& name ) const;
        IVar::Ptr getVar( const std::string& name ) const;
        typedef std::map< std::string, IVar::Ptr >::const_iterator VarIterator;
        VarIterator varsBegin() const;
        VarIterator varsEnd() const;
        void addVar( const IVar::Ptr& var );
        void set( const std::string& name, Val val ) ;
        Val get( const std::string& name ) const;
        
        template <typename T>
        T get( const std::string& name ) const
        {
          FHE_ASSERT_MSG( hasVar( name ) || hasFunc( name ), "unknown var %s", name.c_str() );
          Val val = get( name );
          FHE_ASSERT_MSG( val.is<T>(), "var type mismatch got %s expected %s", val.type().c_str(), typeid(T).name() );
          return val.get<T>();
        }
        
        template <typename T>
        T get( const std::string& name, T def ) const
        {
          if ( hasVar( name ) || hasFunc( name ) )
          {
            Val val = get( name );
            if ( val.is<T>() )
            {
              return val.get<T>();
            }
          }
          return def;
        }
        
        #define CALL_arg( z, n, unused ) BOOST_PP_CAT( A, n ) BOOST_PP_CAT( a, n )
        
        #define CALL_iter( z, n, unused ) \
          Val call( const std::string& name BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, Val a ) ) \
          { \
            FHE_ASSERT_MSG( hasFunc( name ), "unable to call unknown func %s", name.c_str() ); \
            return getFunc( name )->call( buildArgs( BOOST_PP_ENUM_PARAMS( n, a ) ) ); \
          }
            
        BOOST_PP_REPEAT( FHE_ARGS, CALL_iter, ~ )
        
        #undef CALL_iter
        
        #define PUBLISH_iter( z, n, unused ) \
          void publish( const std::string& name BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, Val a ) ) \
          { \
            if ( hasFunc( name ) ) \
            { \
              call( name BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, a ) ); \
            } \
            for ( std::vector< Ptr >::const_iterator child = children_.begin(); child != children_.end(); ++child ) \
            { \
              (*child)->publish( name BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, a ) ); \
            } \
            if ( hasFunc( "un" + name ) ) \
            { \
              call( "un" + name BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, a ) ); \
            } \
          }
              
        BOOST_PP_REPEAT( FHE_ARGS, PUBLISH_iter, ~ )
        
        #undef PUBLISH_iter
        
        #undef CALL_arg
        
      private:
        Node( const Node& );
        void operator=( const Node& );
      
        friend void boost::intrusive_ptr_add_ref( Node* node );
        friend void boost::intrusive_ptr_release( Node* node );
        
        size_t refCount_;
        std::string module_;
        std::string type_;
        std::vector< Ptr > children_;
        Node* parent_;
        std::set< std::string > supers_;
        std::map< std::string, IFunc::Ptr > funcs_;
        std::map< std::string, IVar::Ptr > vars_;

        #define BUILDARGS_append( z, n, unused ) \
          args.push_back( BOOST_PP_CAT( a, n ) );
        
        #define BUILDARGS_iter( z, n, unused ) \
          std::vector< Val > buildArgs( BOOST_PP_ENUM_PARAMS( n, Val a ) ) \
          { \
            std::vector< Val > args; \
            BOOST_PP_REPEAT( n, BUILDARGS_append, ~ ) \
            return args; \
          }
            
        BOOST_PP_REPEAT( FHE_ARGS, BUILDARGS_iter, ~ )
        
        #undef BUILDARGS_iter
        #undef BUILDARGS_append
    };
  }
}

#endif
